#include "RendererHelper.hpp"
#include "../Graphics/OpenGLTools.hpp"
#include <Utility/Timer.hpp>
#ifdef ZZZ_LIB_BOOST
namespace zzz{

RendererHelper::RendererHelper()
:zNear(0.1), zFar(1000), depthbuffer(GL_DEPTH_COMPONENT)
{
}

RendererHelper::~RendererHelper()
{
}

void RendererHelper::RenderCubemap(TextureCube &t, TriMesh &o,Cartesian3DCoordf &c,Shader &s)
{
  fbo.Bind();
  fbo.AttachTexture(depthbuffer, GL_DEPTH_ATTACHMENT);
  GLViewport::Set(0,0,t.width_,t.height_);

  depthbuffer.Create(t.width_,t.width_);

  CHECK_GL_ERROR();

  s.Begin();
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPerspective(90,1,zNear,zFar);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  CHECK_GL_ERROR();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_X,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]+1,c.v[1],c.v[2],0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_X,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]-1,c.v[1],c.v[2],0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_Y,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]+1,c.v[2],0,0,1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]-1,c.v[2],0,0,-1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_Z,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]+1,0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,t.GetID());
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]-1,0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  o.DrawElement(MESH_POS|MESH_NOR);

  fbo.Unattach(GL_COLOR_ATTACHMENT0);

  CHECK_GL_ERROR();

  glMatrixMode(GL_MODELVIEW);
  CHECK_GL_ERROR();
  glPopMatrix();
  CHECK_GL_ERROR();
  glMatrixMode(GL_PROJECTION);
  CHECK_GL_ERROR();
  glPopMatrix();
  CHECK_GL_ERROR();

  s.End();
  CHECK_GL_ERROR();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.Unattach(GL_DEPTH_ATTACHMENT);
  fbo.Disable();
  CHECK_GL_ERROR();

  GLViewport::Restore();
  CHECK_GL_ERROR();
}

void RendererHelper::RenderCubemap(TextureCube &t, Cartesian3DCoordf &c, boost::function<void (void)> drawfunc)
{
  GLViewport::Set(0,0,t.width_,t.height_);

  fbo.Bind();
  fbo.AttachTexture(depthbuffer, GL_DEPTH_ATTACHMENT);
  depthbuffer.Create(t.width_,t.width_);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPerspective(90,1,zNear,zFar);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  GLuint tex=t.GetID();
  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_X,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]+1,c.v[1],c.v[2],0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_X,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]-1,c.v[1],c.v[2],0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_Y,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]+1,c.v[2],0,0,1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]-1,c.v[2],0,0,-1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_POSITIVE_Z,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]+1,0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,tex,GL_COLOR_ATTACHMENT0_EXT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]-1,0,-1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.Unattach(GL_DEPTH_ATTACHMENT);
  fbo.Disable();

  GLViewport::Restore();
}

void RendererHelper::RenderCubemap(Texture2D *t, Cartesian3DCoordf &c, boost::function<void (void)> drawfunc)
{
  GLViewport::Set(0,0,t[0].GetWidth(),t[0].GetHeight());

  depthbuffer.Create(t[0].GetWidth(),t[0].GetHeight());
  fbo.Bind();
  fbo.AttachTexture(depthbuffer, GL_DEPTH_ATTACHMENT);

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPerspective(90,1,zNear,zFar);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[0],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]+1,c.v[1],c.v[2],0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[1],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]-1,c.v[1],c.v[2],0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[2],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]+1,c.v[2],0,0,+1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[3],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]-1,c.v[2],0,0,-1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[4],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]+1,0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[5],GL_COLOR_ATTACHMENT0);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]-1,0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  CHECK_GL_ERROR();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.Unattach(GL_DEPTH_ATTACHMENT);
  fbo.Disable();

  GLViewport::Restore();
}

void RendererHelper::RenderCubemap(Texture2D *t, Texture2D *depth, Cartesian3DCoordf &c, boost::function<void (void)> drawfunc)
{
  GLViewport::Set(0,0,t[0].GetWidth(),t[0].GetHeight());

  for (zuint i=0; i<6; i++) {
    depth[i].ChangeInternalFormat(GL_DEPTH_COMPONENT);
    depth[i].Create(t[i].GetWidth(),t[i].GetHeight());
  }
  fbo.Bind();

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPerspective(90,1,zNear,zFar);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[0],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[0], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]+1,c.v[1],c.v[2],0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[1],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[1], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0]-1,c.v[1],c.v[2],0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[2],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[2], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]+1,c.v[2],0,0,+1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[3],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[3], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1]-1,c.v[2],0,0,-1);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[4],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[4], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]+1,0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(t[5],GL_COLOR_ATTACHMENT0);
  fbo.AttachTexture(depth[5], GL_DEPTH_ATTACHMENT);
  glLoadIdentity();
  gluLookAt(c.v[0],c.v[1],c.v[2],c.v[0],c.v[1],c.v[2]-1,0,+1,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawfunc();

  CHECK_GL_ERROR();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  fbo.Unattach(GL_DEPTH_ATTACHMENT);
  fbo.Disable();

  GLViewport::Restore();
}


void RendererHelper::RenderToTexture(Texture2D &t, boost::function<void (void)> setupcamera, boost::function<void (void)> drawfunc)
{
  fbo.Bind();
  CHECK_GL_ERROR();
  GLViewport::Set(0,0,t.GetWidth(),t.GetHeight());

  CHECK_GL_ERROR();
  depthbuffer.Create(t.GetWidth(),t.GetHeight());
  CHECK_GL_ERROR();
  fbo.AttachTexture(depthbuffer, GL_DEPTH_ATTACHMENT);
  CHECK_GL_ERROR();

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  setupcamera();
  CHECK_GL_ERROR();

  fbo.AttachTexture(t, GL_COLOR_ATTACHMENT0);
  CHECK_GL_ERROR();
  fbo.CheckStatus();
  CHECK_GL_ERROR();
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  CHECK_GL_ERROR();
  drawfunc();
  CHECK_GL_ERROR();

  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  CHECK_GL_ERROR();
  fbo.Unattach(GL_COLOR_ATTACHMENT0);
  CHECK_GL_ERROR();
  fbo.Unattach(GL_DEPTH_ATTACHMENT);
  CHECK_GL_ERROR();
  fbo.Disable();
  CHECK_GL_ERROR();

  GLViewport::Restore();
}

Colorui8 RendererHelper::ZuintToColor(zuint32 x)
{
  return Colorui8(x & 0xFF, (x >> 8) & 0xFF, (x >> 16) & 0xFF);
}

zuint32 RendererHelper::ColorToZuint(const Vector3ui8 &c)
{
  return c.r() + (c.g() << 8) + (c.b() << 16);
}

zuint32 RendererHelper::ColorToZuint(const Vector4ui8 &c)
{
  return c.r() + (c.g() << 8) + (c.b() << 16) + (c.a() << 24);
}

}
#endif // ZZZ_LIB_BOOST